package com.homelock.common.util;

import java.io.UnsupportedEncodingException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.text.ParseException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.lang3.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.pi.base.exception.ServiceException;
import com.pi.base.util.lang.ByteUtil;
import com.pi.base.util.lang.IntegerUtil;
import com.pi.base.util.lang.LongUtil;

/** 
* @author chenmfa
* @version 创建时间：2017年9月29日 下午1:30:20 
* @description
*/
public class LockEncryptUtil {

  private static final Logger logger = LoggerFactory.getLogger(LockEncryptUtil.class);
 // public static byte[] IDENTITY_PASSWORD = new byte[]{0x18, (byte)0xA0, 0x3B, 0x11, (byte)0xF2, 0x42, 0x79, (byte)0xFF, (byte)0xC8, (byte)0xAD, (byte)0xE0, 0x40, (byte)0xDC, 0x35, 0x69, (byte)0x5F};
  public static String DEFAULT_CHARSET = "UTF-8";
  
  private static final byte[] iv = new byte[]{0x60, 0x5A, 0x4E, (byte) 0xB0, (byte) 0x8B, (byte) 0x83, 0x42, 0x48, 0x3F, (byte) 0xF4,(byte) 0xC6, 0x23, (byte) 0x9A, 0x37, (byte) 0x99, (byte) 0xF7};
                
  private static final long[] OTP_TABLE = new long[] {Integer.toUnsignedLong(0xcaed1122), Integer.toUnsignedLong(0x72ed1462), Integer.toUnsignedLong(0x692ab305), Integer.toUnsignedLong(0xac03827a), Integer.toUnsignedLong(0x08cf0bd2), Integer.toUnsignedLong(0x153ef8c2), Integer.toUnsignedLong(0x50ea25c9), Integer.toUnsignedLong(0x3054ca8e), Integer.toUnsignedLong(0x40cadb21), Integer.toUnsignedLong(0x5ace0397)};


  /**
   * @description 获取未加密的设备身份证书
   * @param lockId 设备id
   * @param regCode 设备注册时生成(随机码)
   * @param lockTime 锁具注册时间 , 不除以1000,方法调用由开发者控制
   * @return byte[] 身份证书
   * @throws UnsupportedEncodingException
   */
  public static byte[] getDevIdentity(byte[] lockId,String regCode, long lockTime) 
      throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, 
      NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{
    if(null == lockId || lockId.length != 7){
      throw new ServiceException("设备Id长度不正确");
    }else if(null == regCode || regCode.length() !=4){
      throw new ServiceException("注册码长度不正确");
    }
    byte[] id = new byte[16];
    System.arraycopy(lockId, 0, id, 0, 7);
    System.arraycopy(regCode.getBytes(DEFAULT_CHARSET), 0, id, 7, 4);
    System.arraycopy(long2Bytes(lockTime), 0, id, 11, 4);
    //填充位
    id[15] = (byte)0xAA;
    return id;
  }

  /**
   * @description 获取加密后的设备身份证书
   * @param lockId 设备id
   * @param regCode 设备注册时生成(随机码)
   * @param lockTime 锁具注册时间
   * @return byte[] 身份证书
   * @throws UnsupportedEncodingException
   * @throws InvalidKeyException
   * @throws NoSuchAlgorithmException
   * @throws NoSuchPaddingException
   * @throws IllegalBlockSizeException
   * @throws BadPaddingException
   * @throws InvalidAlgorithmParameterException
   */
  public static byte[] getCiperedDevIdentity(byte[] lockId,String regCode, long lockTime,byte[] identityPwd)
          throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException,
          NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException{
    return AES_CFB_NoPaddingEncrypt(
            identityPwd,getDevIdentity(lockId, regCode, lockTime));
  }

  /*public static byte[] getCiperedDevIdentity(byte[] lockId,String regCode, long lockTime)
      throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, 
      NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException{    
    return AES_CFB_NoPaddingEncrypt(
        IDENTITY_PASSWORD,getDevIdentity(lockId, regCode, lockTime));
  }*/
  /**
   * @description 获取加密后的设备身份证书
   * @param lockId 设备id
   * @param regCode 设备注册时生成(随机码)
   * @param lockTime 锁具注册时间
   * @return byte[] 身份证书
   * @throws UnsupportedEncodingException
   * @throws InvalidKeyException
   * @throws NoSuchAlgorithmException
   * @throws NoSuchPaddingException
   * @throws IllegalBlockSizeException
   * @throws BadPaddingException
   * @throws InvalidAlgorithmParameterException 
   */
  public static byte[] getCiperedDevIdentity(byte[] lockIdentity,byte[] identityPwd)
          throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException,
          NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException{
    return AES_CFB_NoPaddingEncrypt(
            identityPwd, lockIdentity);
  }
/*  public static byte[] getCiperedDevIdentity(byte[] lockIdentity)
      throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException, 
      NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException{    
    return AES_CFB_NoPaddingEncrypt(
        IDENTITY_PASSWORD, lockIdentity);
  }*/
  
  /**
   * @description 将数字时间转换成4字节数组
   * @param num 数值
   * @return byte[] 字节数组
   */
  public static byte[] long2Bytes(long num) {
    byte[] by = new byte[4];
    for (int ix = 0; ix < by.length; ++ix){
    int offset = by.length * 8 - (ix + 1) * 8;
      by[ix] = (byte) ((num >> offset) & 0xff);
    }
    return by;
  }

  /**
   * @description 获取未加密的身份码
   * @param lockId 设备id
   * @param regCode 设备注册时生成(随机码)
   * @param lockTime 锁具注册时间
   * @return byte[] 设备身份码
   * @throws UnsupportedEncodingException
   * @throws ParseException 
   */
  public static byte[] getDevCodeForXiaoPi(byte[] lockId,long lockTime)
      throws UnsupportedEncodingException, ParseException{
    byte[] devCode = new byte[14];
    devCode[0] = (byte)0x00;
    System.arraycopy(lockId, 0, devCode, 1, 7);
    System.arraycopy(long2Bytes(lockTime), 0, devCode, 8, 4);//
    devCode[12] = (byte)0xAA;
    devCode[13] = (byte)0xAA;
    return devCode;
  }
  
  /**
   * @description 获取未加密的身份码
   * @param lockId 设备id
   * @param regCode 设备注册时生成(随机码)
   * @param lockTime 锁具注册时间
   * @return byte[] 设备身份码
   * @throws UnsupportedEncodingException
   * @throws ParseException 
   */
  public static byte[] getDevCode(byte[] lockId,long userId)
      throws UnsupportedEncodingException, ParseException{
    byte[] devCode = new byte[16];
    devCode[0] = (byte)0x00;
    System.arraycopy(lockId, 0, devCode, 1, 7);
    System.arraycopy(LongUtil.long2Bytes(userId), 0, devCode, 8, 8);//
    return devCode;
  }
  
  /**
   * @description 获取加密后的身份码
   * @param lockId 设备id
   * @param regCode 设备注册时生成(随机码)
   * @param lockTime 锁具注册时间
   * @param userPassword 用户密码
   * @return
   * @throws InvalidKeyException
   * @throws UnsupportedEncodingException
   * @throws NoSuchAlgorithmException
   * @throws NoSuchPaddingException
   * @throws IllegalBlockSizeException
   * @throws BadPaddingException
   * @throws InvalidAlgorithmParameterException 
   * @throws ParseException 
   */
  public static byte[] getCiperedDevCode(byte[] lockId,String regCode, long lockTime, String userPassword) 
      throws InvalidKeyException, UnsupportedEncodingException, 
      NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, 
      BadPaddingException, InvalidAlgorithmParameterException, ParseException{
    byte[] devIdentity = getDevIdentity(lockId, regCode, lockTime);
    byte[] devCode = getDevCode(lockId,lockTime);
    return getCiperedDevCode(devCode, devIdentity, userPassword);
  }
  
  
  /**
   * @description 获取加密后的身份码
   * @param devCode 设备身份码
   * @param devIdentity 设备身份证书
   * @return byte[]
   * @throws InvalidKeyException
   * @throws UnsupportedEncodingException
   * @throws NoSuchAlgorithmException
   * @throws NoSuchPaddingException
   * @throws IllegalBlockSizeException
   * @throws BadPaddingException
   * @throws InvalidAlgorithmParameterException 
   */
  public static byte[] getCiperedDevCode(byte[] devCode, byte[] devIdentity) 
      throws InvalidKeyException, UnsupportedEncodingException, 
      NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, 
      BadPaddingException, InvalidAlgorithmParameterException{
    logger.info("\r\n传入：devCode： {}, \r\ndevIdentity:{}, \r\n",
        ByteUtil.byteToHex(devCode),ByteUtil.byteToHex(devIdentity));
    byte[] key = new byte[16];
    System.arraycopy(devIdentity, 0, key, 0, 16);
    return AES_CFB_NoPaddingEncrypt(key, devCode);
  }
  
  /**
   * @description 获取加密后的身份码
   * @param devCode 设备身份码
   * @param devIdentity 设备身份证书
   * @param userPassword 用户密码
   * @return byte[]
   * @throws InvalidKeyException
   * @throws UnsupportedEncodingException
   * @throws NoSuchAlgorithmException
   * @throws NoSuchPaddingException
   * @throws IllegalBlockSizeException
   * @throws BadPaddingException
   * @throws InvalidAlgorithmParameterException 
   */
  public static byte[] getCiperedDevCode(byte[] devCode, byte[] devIdentity, String userPassword) 
      throws InvalidKeyException, UnsupportedEncodingException, 
      NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, 
      BadPaddingException, InvalidAlgorithmParameterException{
    logger.info("\r\n传入：devCode： {}, \r\ndevIdentity:{}, \r\nuserPassword: {}",ByteUtil.byteToHex(devCode),ByteUtil.byteToHex(devIdentity), userPassword);
    byte[] key = new byte[16];
    System.arraycopy(devIdentity, 0, key, 0, 16);
    return AES_CFB_NoPaddingEncrypt(
        userPassword.substring(0, 16).getBytes(DEFAULT_CHARSET),
        AES_CFB_NoPaddingEncrypt(key, devCode));
  }
  
  /**
   * @description AES_CFB_NoPadding加密
   * @param password
   * @param data
   * @return
   * @throws NoSuchAlgorithmException
   * @throws NoSuchPaddingException
   * @throws InvalidKeyException
   * @throws IllegalBlockSizeException
   * @throws BadPaddingException
   * @throws UnsupportedEncodingException
   * @throws InvalidAlgorithmParameterException
   */
  public static byte[] AES_CFB_NoPaddingEncrypt(byte[] password,byte[] data) 
      throws NoSuchAlgorithmException, NoSuchPaddingException, 
      InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
      UnsupportedEncodingException, InvalidAlgorithmParameterException{
    SecretKeySpec key = new SecretKeySpec(password, "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(iv);
    Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");// 创建密码器 
    cipher.init(Cipher.ENCRYPT_MODE, key,ivSpec);// 初始化 
    byte[] result = cipher.doFinal(data);
//    System.out.println(ByteUtil.byteToHex(password)+"|"+ByteUtil.byteToHex(data)+"|"+ByteUtil.byteToHex(result));
    return result;
  }
  /**
   * @description AES_CFB_NoPadding解密
   * @param password
   * @param data
   * @return
   * @throws Exception
   */
  public static byte[] AES_CFB_NoPaddingDecrypt(byte[] password,byte[] data) throws Exception{
    SecretKeySpec key = new SecretKeySpec(password, "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(iv);
    Cipher cipher = Cipher.getInstance("AES/CFB/NoPadding");// 创建密码器 
    cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);// 初始化 
    byte[] result = cipher.doFinal(data);
    return result;
  }
  
  public static byte[] AES_128_CBC_PCKS_Decrypt(byte[] password, byte[] data, byte[] iv) 
      throws Exception{
    if(null == password || password.length%16 != 0){
      throw new ServiceException("加密密钥必须为16位的");
    }
    KeyGenerator kgen = KeyGenerator.getInstance("AES");
    kgen.init(128);
//    SecretKey skey = kgen.generateKey();
    Security.addProvider(new BouncyCastleProvider());
    SecretKeySpec key = new SecretKeySpec(password, "AES");
    IvParameterSpec ivSpec = new IvParameterSpec(iv);
    System.out.println("IV：" + ByteUtil.byteToHex(iv));
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding","BC");// 创建密码器 
    cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);
    return cipher.doFinal(data);
  }

  /**
   * 生成一次性临时密码 devId 设备ID（16进制字符串） devCert 设备证书（16字节的字节数组） time 当前时间戳（秒）
   */
  public static String getOneTimePassword(String devCode, int[] devCert, Long time) {
    byte[] devCodeBytesSigned = ByteUtil.hexToByte(devCode);
    long[] devCodeBytes = new long[devCodeBytesSigned.length];
    for(int i = 0; i < devCodeBytesSigned.length; i ++) {
        devCodeBytes[i] = devCodeBytesSigned[i] & 0xFF;
    }
    long iv1 = (devCodeBytes[0] << 24) | ((devCodeBytes[6] ^ devCodeBytes[1]) << 16)
        | ((devCodeBytes[5] ^ devCodeBytes[2]) << 8) | ((devCodeBytes[4] ^ devCodeBytes[3]) << 0);
    long iv2 = 0;
    for (int i = 0; i < 4; i++) {
      iv2 |= (devCert[i] ^ devCert[4 + i] ^ devCert[8 + i] ^ devCert[12 + i]) << i;
    }
    time = time / 300;
    long opt = OTP_TABLE[(int) (time % 10)];
    Long pwd = ((time ^ opt ^ iv1 ^ iv2) >>> 0) % 100000000;
    if(pwd < 0) {
      pwd *= -1;
    }
    return StringUtils.leftPad(pwd.toString(), 8, "0");
  }
  
  public static String getOneTimePassword(String devCode, int[] devCert, Long time, long[] otpTable) {
      byte[] devCodeBytesSigned = ByteUtil.hexToByte(devCode);
      long[] devCodeBytes = new long[devCodeBytesSigned.length];
      for(int i = 0; i < devCodeBytesSigned.length; i ++) {
          devCodeBytes[i] = devCodeBytesSigned[i] & 0xFF;
      }
      long iv1 = (devCodeBytes[0] << 24) | ((devCodeBytes[6] ^ devCodeBytes[1]) << 16)
          | ((devCodeBytes[5] ^ devCodeBytes[2]) << 8) | ((devCodeBytes[4] ^ devCodeBytes[3]) << 0);
      long iv2 = 0;
      for (int i = 0; i < 4; i++) {
        iv2 |= (devCert[i] ^ devCert[4 + i] ^ devCert[8 + i] ^ devCert[12 + i]) << i;
      }
      time = time / 300;
      long opt = otpTable[(int) (time % 10)];
      Long pwd = ((time ^ opt ^ iv1 ^ iv2) >>> 0) % 100000000;
      if(pwd < 0) {
        pwd *= -1;
      }
      return StringUtils.leftPad(pwd.toString(), 8, "0");
    }
  
    public static String getOneTimePassword(int adminPwd, Long time, long[] otpTable) {
        time = time / 300;
        Long tmp1 = time ^ otpTable[(int) (time % 10)];
        Long tmp2 = tmp1 ^ adminPwd;
        Long tmp3 = tmp2 ^ otpTable[(int) ((tmp2 >>> 0) % 8)];
        return StringUtils.leftPad(String.valueOf((tmp3 >>> 0) % 100000000), 8);
    }
    static int[] OTPTab = new int[] {0x7aed1122, 0x72ed1462, 0x192ab305, 0x6c03827a, 0x08cf0bd2, 0x153ea8c2, 0x50ea27c9, 0x3054ca8e, 0x40caeb21, 0x1ace0397};

    public static void main(String[] args) {
        long[] otp = LongUtil.byte2Long(IntegerUtil.toByteArray(OTPTab), 4);
        System.out.println(getOneTimePassword(123456, System.currentTimeMillis()/1000, otp));
    }
}
